/**
 * Copyright (C) 2005-2009 Alfresco Software Limited.
 *
 * This file is part of the Spring Surf Extension project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.augurit.smartform.util;

import java.io.IOException;
import java.io.StringWriter;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;

import sun.org.mozilla.javascript.internal.Context;
import sun.org.mozilla.javascript.internal.IdScriptableObject;
import sun.org.mozilla.javascript.internal.NativeArray;
import sun.org.mozilla.javascript.internal.NativeJavaObject;
import sun.org.mozilla.javascript.internal.NativeObject;

/**
 * Collection of JSON Utility methods. This class is immutable.
 * 
 * @author Roy Wetherall
 */
public class JavaScriptJsonUtils {
	/**
	 * Converts a given JavaScript native object and converts it to the relevant
	 * JSON string.
	 * 
	 * @param object
	 *            JavaScript object
	 * @return String JSON
	 * @throws IOException 
	 * @throws JSONException
	 */
	public static String toJSONString(Object object) throws Exception {
		String str="";
		StringWriter writer=new StringWriter();
		JsonGenerator generator=new JsonFactory().createJsonGenerator(writer);
		
		if (object instanceof NativeArray) {
			nativeArrayToJSONString((NativeArray) object, generator);
		} else if (object instanceof NativeObject) {
			nativeObjectToJSONString((NativeObject) object, generator);
		} else {
			// TODO what else should this support?
			throw new Exception(
					"Only native objects and arrays are currently supported by the toJSONString method.");
		}
		generator.close();
		str=writer.toString();
		writer.close();
		return str;
	}

	/**
	 * Takes a JSON string and converts it to a native java script object
	 * 
	 * @param jsonString
	 *            a valid json string
	 * @return NativeObject the created native JS object that represents the
	 *         JSON object
	 * @throws JSONException
	 */
/*	public NativeObject toObject(String jsonString) throws JSONException {
		// TODO deal with json array stirngs

		// Parse JSON string
		JSONObject jsonObject = new JSONObject(jsonString);

		// Create native object
		return toObject(jsonObject);
	}*/

	/**
	 * Takes a JSON object and converts it to a native JS object.
	 * 
	 * @param jsonObject
	 *            the json object
	 * @return NativeObject the created native object
	 * @throws JSONException
	 */
	/*public NativeObject toObject(JSONObject jsonObject) throws JSONException {
		// Create native object
		NativeObject object = new NativeObject();

		Iterator<String> keys = jsonObject.keys();
		while (keys.hasNext()) {
			String key = (String) keys.next();
			Object value = jsonObject.get(key);
			if (value instanceof JSONObject) {
				object.put(key, object, toObject((JSONObject) value));
			} else {
				object.put(key, object, value);
			}
		}

		return object;
	}*/

	/**
	 * Build a JSON string for a native object
	 * 
	 * @param nativeObject
	 * @param json
	 * @throws IOException 
	 * @throws JsonGenerationException 
	 * @throws JSONException
	 */
	private static void nativeObjectToJSONString(NativeObject nativeObject,
			JsonGenerator generator) throws JsonGenerationException, IOException {
		generator.writeStartObject();

		Object[] ids = nativeObject.getIds();
		for (Object id : ids) {
			String key = id.toString();
			Object value = nativeObject.get(key, nativeObject);
			
			//暂时去掉InterpretedFunction
			if (!value.getClass().getName().equals("sun.org.mozilla.javascript.internal.InterpretedFunction")){
				generator.writeFieldName(key);
				
				valueToJSONString(value, generator);
			}
		}

		generator.writeEndObject();
	}

	/**
	 * Build JSON string for a native array
	 * 
	 * @param nativeArray
	 * @param json
	 * @throws IOException 
	 * @throws JsonGenerationException 
	 */
	private static void nativeArrayToJSONString(NativeArray nativeArray,
			JsonGenerator generator) throws JsonGenerationException, IOException {
		Object[] propIds = nativeArray.getIds();
		if (isArray(propIds) == true) {
			
			generator.writeStartArray();

			for (int i = 0; i < propIds.length; i++) {
				Object propId = propIds[i];
				if (propId instanceof Integer) {
					Object value = nativeArray.get((Integer) propId,
							nativeArray);
					valueToJSONString(value, generator);
				}
			}
			
			generator.writeEndArray();
		} else {
			generator.writeStartObject();

			for (Object propId : propIds) {
				Object value = nativeArray.get(propId.toString(), nativeArray);
				generator.writeFieldName(propId.toString());
				valueToJSONString(value, generator);
			}

			generator.writeEndObject();
		}
	}

	/**
	 * Look at the id's of a native array and try to determine whether it's
	 * actually an Array or a HashMap
	 * 
	 * @param ids
	 *            id's of the native array
	 * @return boolean true if it's an array, false otherwise (ie it's a map)
	 */
	private static boolean isArray(Object[] ids) {
		boolean result = true;
		for (Object id : ids) {
			if (id instanceof Integer == false) {
				result = false;
				break;
			}
		}
		return result;
	}

	/**
	 * Convert value to JSON string
	 * 
	 * @param value
	 * @param json
	 * @throws IOException 
	 * @throws JsonGenerationException 
	 * @throws JSONException
	 */
	private static void valueToJSONString(Object value, JsonGenerator generator) throws JsonGenerationException, IOException {
		if (value instanceof IdScriptableObject
				&& ((IdScriptableObject) value).getClassName().equals("Date") == true) {
			// Get the UTC values of the date
			Object year = NativeObject.callMethod((IdScriptableObject) value,
					"getUTCFullYear", null);
			Object month = NativeObject.callMethod((IdScriptableObject) value,
					"getUTCMonth", null);
			Object date = NativeObject.callMethod((IdScriptableObject) value,
					"getUTCDate", null);
			Object hours = NativeObject.callMethod((IdScriptableObject) value,
					"getUTCHours", null);
			Object minutes = NativeObject.callMethod(
					(IdScriptableObject) value, "getUTCMinutes", null);
			Object seconds = NativeObject.callMethod(
					(IdScriptableObject) value, "getUTCSeconds", null);
			Object milliSeconds = NativeObject.callMethod(
					(IdScriptableObject) value, "getUTCMilliseconds", null);

			// Build the JSON object to represent the UTC date
			generator.writeStartObject();
			generator.writeStringField("zone", "UTC");
			generator.writeNumberField("year", (Double)year);
			generator.writeNumberField("month", (Double)month);
			generator.writeNumberField("date", (Double)date);
			generator.writeNumberField("hours", (Double)hours);
			generator.writeNumberField("minutes", (Double)minutes);
			generator.writeNumberField("seconds", (Double)seconds);
			generator.writeNumberField("milliSeconds", (Double)milliSeconds);
			
			generator.writeEndObject();
		} else if (value instanceof NativeJavaObject) {
			Object javaValue = Context.jsToJava(value, Object.class);
			generator.writeObject(javaValue);
		} else if (value instanceof NativeArray) {
			// Output the native object
			nativeArrayToJSONString((NativeArray) value, generator);
		} else if (value instanceof NativeObject) {
			// Output the native array
			nativeObjectToJSONString((NativeObject) value, generator);
		} else {
			generator.writeObject(value);
		}
	}
}
